/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.jwt.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.star.framework.autoconfigure.security.SecurityProperties;
import cn.star.framework.identity.UserIdentityService;
import cn.star.framework.jwt.core.SecurityGrantedAuthority;
import cn.star.framework.jwt.core.SecurityUser;
import cn.star.framework.rbac.entity.Role;
import cn.star.framework.rbac.entity.User;
import cn.star.framework.rbac.service.AuthorizationService;
import cn.star.framework.rbac.service.RoleService;
import cn.star.framework.rbac.service.UserService;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * 用户身份信息服务实现<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Service
public class UserDetailsServiceImpl implements UserIdentityService<Role, User>, UserDetailsService {
  @Autowired private UserService userService;
  @Autowired private RoleService roleService;
  @Autowired private AuthorizationService authorizationService;
  @Autowired private SecurityProperties security;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = this.load(username);
    // 抛出这个异常(throw new UsernameNotFoundException(username);)依然会进行密码的验证，不能获取到正确的错误信息
    Assert.isFalse(user == null, "Invalid account: {}", username);

    long currentTime = System.currentTimeMillis();
    boolean enabled = this.isEnabled(user),
        accountNonExpired = this.isAccountNonExpired(user, currentTime),
        credentialsNonExpired = this.isCredentialsNonExpired(user, currentTime),
        accountNonLocked = this.isAccountNonLocked(user);

    Assert.isTrue(enabled, "{} Account disable, Login ban", username);
    Assert.isTrue(accountNonExpired, "{} Account expired, Login ban", username);
    Assert.isTrue(credentialsNonExpired, "{} Password expired, Login ban", username);
    Assert.isTrue(accountNonLocked, "{} Account lock, Login ban", username);

    // 取得角色列表
    List<Role> roles = this.getUserIdentities(user);
    List<GrantedAuthority> authorities = new ArrayList<>();
    if (CollectionUtil.isNotEmpty(roles)) {
      authorities =
          roles.stream()
              .map(role -> new SecurityGrantedAuthority(role.getIdentityName()))
              .collect(Collectors.toList());
    }

    // 没有角色和资源的账号视为未授权用户，禁止登录系统
    Assert.isFalse(
        CollectionUtil.isEmpty(roles) && CollectionUtil.isEmpty(authorities),
        "Unauthorized user: {}",
        username);

    SecurityUser su =
        new SecurityUser(
            user.getUsername(),
            user.getPassword(),
            enabled,
            accountNonExpired,
            credentialsNonExpired,
            accountNonLocked,
            authorities);

    su.setRbacUser(user);
    su.setRbacRoles(roles);

    return su;
  }

  private boolean isEnabled(User user) {
    return !security.isEnabled() || user.isEnabled();
  }

  private boolean isAccountNonExpired(User user, long currentTime) {
    if (user.getAccountExpiredTime() != null && security.isEnabled()) {
      return user.getAccountExpiredTime().getTime() - currentTime >= 0;
    }

    return true;
  }

  private boolean isCredentialsNonExpired(User user, long currentTime) {
    if (user.getPasswordModifyTime() != null && security.isEnabled()) {
      int day = security.getPasswordExpiredDay();
      if (day > 0) {
        return (user.getPasswordModifyTime().getTime() + (long) day * 24 * 60 * 60 * 1000)
                - currentTime
            >= 0;
      }
    }

    return true;
  }

  private boolean isAccountNonLocked(User user) {
    if (security.isEnabled()) {
      // 密码错误次数当达到最大次数时，也视为账号被锁定
      return user.isAccountNonLocked()
          && user.getPasswordErrorTimes() < security.getPasswordErrorTimes();
    }

    return true;
  }

  @Override
  public List<Role> getUserIdentities(String username) {
    User user = this.load(username);

    return user != null ? this.getUserIdentities(user) : new ArrayList<>();
  }

  @Override
  public List<Role> getUserIdentities(User user) {
    List<Role> list = new ArrayList<>();
    if (user != null) {
      Set<String> roleIds = authorizationService.getUserAuthorizationGroupIds(user.getId());
      if (CollectionUtil.isNotEmpty(roleIds)) {
        list = roleService.findAllByIds(roleIds);
      }
    }

    return list;
  }

  @Override
  public User getCurrentUser() {
    String username =
        (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

    return new User(username);
  }

  @Override
  public User load(String username) {
    return userService.findByUsername(username);
  }
}
